package edu.pw.p2pclient.networking;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Random;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import edu.pw.p2pclient.R;
import edu.pw.p2pclient.data.DownloadNode;
import edu.pw.p2pclient.data.RemoteDeviceInfo;
import edu.pw.p2pclient.logic.DownloadManager;
import edu.pw.p2pclient.networking.messages.Message;
import edu.pw.p2pclient.networking.messages.Message.MsgType;
import edu.pw.p2pclient.networking.messages.ResourcePartRequestMessage;
import edu.pw.p2pclient.networking.messages.ResourcePartResponse;
import edu.pw.p2pclient.utils.ApplicationContext;

public class DownloadTask extends AsyncTask<Void, Void, ResourcePartResponse> {

	private final static Random RANDOMIZER = new Random();

	private String TAG;

	private int mPart;
	private DownloadNode mNode;

	public DownloadTask(DownloadNode node, int part) {

		mNode = node;
		mPart = part;
		TAG = "DT (" + mNode.getName() + ") " + mPart;
	}

	public int getPart() {
		return mPart;
	}

	@Override
	protected ResourcePartResponse doInBackground(Void... params) {

		ArrayList<RemoteDeviceInfo> devices = CommunicationService
				.getInstance().getDevicesWithResource(mNode);
		if (devices.size() != 0) {
			Log.i(TAG, "Found " + devices.size() + " device(s)!");
			ObjectInputStream instr = null;
			Socket socket = null;
			ServerSocket serverSocket = null;
			try {
				serverSocket = new ServerSocket(0);
				serverSocket.setSoTimeout(30000);
				if (sendPartRequest(
						devices.get(RANDOMIZER.nextInt(devices.size())),
						serverSocket.getLocalPort())) {
					Log.i(TAG, "Awaiting for download connection on "
							+ serverSocket.getInetAddress().toString() + ":"
							+ serverSocket.getLocalPort());
					socket = serverSocket.accept();
					serverSocket.close();
					instr = new ObjectInputStream(socket.getInputStream());
					Message msg = (Message) instr.readObject();
					msg.setSenderAddress(socket.getInetAddress());
					if (msg.getMsgType() == MsgType.RESOURCE_PART_RESPONSE)
						return (ResourcePartResponse) msg;
					else if (msg.getMsgType() == MsgType.DENIAL) {
						if (mNode.getSharingInfo().isPasswordProtected()) {
							Log.i(TAG,
									"Denial message received as password is wrong.");
							return new ResourcePartResponse(1);
						} else {
							Log.i(TAG, "Denial message received.");
							return new ResourcePartResponse(2);
						}
					} else
						Log.e(TAG, "Unknown message received.");
					return new ResourcePartResponse(3);
				}
			} catch (SocketTimeoutException e) {
				Log.i(TAG, "Socket timeout.");
			} catch (IOException e) {
				Log.i(TAG, "Socket corrupted - ending handler.");
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} finally {
				try {
					if (instr != null)
						instr.close();
				} catch (IOException ex) {

				}
				try {
					if (socket != null)
						socket.close();
				} catch (IOException ex) {

				}
				try {
					if (serverSocket != null)
						serverSocket.close();
				} catch (IOException ex) {

				}
			}
			return new ResourcePartResponse(4);
		} else {
			Log.i(TAG, "None of known devices has this resource");
			return new ResourcePartResponse(5);
		}
	}

	private boolean sendPartRequest(RemoteDeviceInfo device, int port) {
		synchronized (device) {
			Socket sock = null;
			try {
				sock = device.getSocket();
				if (sock == null || sock.isClosed()) {
					Log.i(TAG, "Sock doesn't exist. I need to create it...");
					sock = new Socket(device.getAddress(),
							CommunicationService.PORT);
					sock.setKeepAlive(true);
					device.setSocket(sock);
				}
				Log.i(TAG, "Sending request to " + device.getAddress()
						+ " to respond on port " + port);
				device.writeObject(new ResourcePartRequestMessage(mNode, mPart,
						port));
				return true;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				try {
					if (sock != null)
						sock.close();
				} catch (IOException ex) {

				}
				return false;
			}
		}
	}

	@Override
	protected void onPostExecute(ResourcePartResponse result) {

		if (result != null && result.getError() == 0)
			DownloadManager.getInstance().onPartDownloadFinished(mNode, mPart,
					result.getData());
		else {
			if (result != null) {
				if (result.getError() == 5) {
					mNode.setState(DownloadNode.QUEUED);
					DownloadManager.getInstance().onPartdownloadFailed(mNode,
							mPart, true);
				} else {
					if (result.getError() == 1) {
						mNode.getSharingInfo().setPassword("");
						mNode.setState(DownloadNode.PAUSED);
						Toast.makeText(
								ApplicationContext.get(),
								ApplicationContext.get().getString(
										R.string.wrong_password_of_resource,
										mNode.getName()), Toast.LENGTH_LONG)
								.show();
					}
					DownloadManager.getInstance().onPartdownloadFailed(mNode,
							mPart, false);
				}
			}

		}
		super.onPostExecute(result);
	}

	@SuppressLint("NewApi")
	public void run(Void... params) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
			this.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
		} else {
			this.execute(params);
		}
	}
}